<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <!-- Copyright 2008 Google Inc.  All rights reserved. -->
  <head>
    <title> Mandelbrot: JavaScript versus Native Client </title>
    <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
    <META HTTP-EQUIV="Expires" CONTENT="-1">
    <style type="text/css">
      canvas { border:solid }
    </style>
    <script type="application/x-javascript" src="nacltest.js">
    <script type="application/x-javascript">
      //<![CDATA[
// A utility function to get the integer value of a user input field.
var getValue = function(id) {
  var element = document.getElementById(id);
  var value = element.value;
  return parseInt(value);
}

// Compute the color for one pixel.
var mandelJavaScript = function(i, j, canvas_width) {
  var cx = (4.0 / canvas_width) * i - 3.0;
  var cy = 1.5 - (3.0 / canvas_width) * j;
  var re = cx;
  var im = cy;
  var count = 0;
  var threshold = 1.0e8;
  while (count < 256 && re * re + im * im < threshold) {
    var new_re = re * re - im * im + cx;
    var new_im = 2 * re * im + cy;
    re = new_re;
    im = new_im;
    ++count;
  }
  var r;
  var g;
  var b;
  if (count < 8) {
    r = 128;
    g = 0;
    b = 0;
  } else if (count < 16) {
    r = 255;
    g = 0;
    b = 0;
  } else if (count < 32) {
    r = 255;
    g = 255;
    b = 0;
  } else if (count < 64) {
    r = 0;
    g = 255;
    b = 0;
  } else if (count < 128) {
    r = 0;
    g = 255;
    b = 255;
  } else if (count < 256) {
    r = 0;
    g = 0;
    b = 255;
  } else {
    r = 0;
    g = 0;
    b = 0;
  }
  return [r, g, b];
}

// Compute the JavaScript mandelbrot picture and render into canvas.
var drawJavaScript = function(canvas_width, tile_width, context) {
  var call_time = 0;
  var canvas_time = 0;
  for (var i = 0; i < canvas_width; ++i) {
    for (var j = 0; j < canvas_width; ++j) {
      var before_call = new Date();
      var arr = mandelJavaScript(i, j, canvas_width);
      var after_call = new Date();
      var difftime = after_call.getTime() - before_call.getTime();
      call_time += difftime;
      before_call = new Date();
      context.fillStyle = 'rgb(' + arr[0] + ',' + arr[1] + ',' + arr[2] + ')';
      context.fillRect(i, j, 1, 1);
      after_call = new Date();
      difftime = after_call.getTime() - before_call.getTime();
      canvas_time += difftime;
    }
  }
  return [call_time, canvas_time];
}

// Compute and display the entire JavaScript mandelbrot picture, including
// printing the gathered timings.
var testJavaScript = function() {
  // Set up the element where the time will be reported.
  var time_element = document.getElementById('js_time');
  time_element.innerHTML = '<b> JavaScript';
  // Get the test parameters.
  var canvas_width = getValue('canvas_width');
  var tile_width = getValue('tile_width');
  // Set up the canvas object and clear it.
  var canvas = document.getElementById('js_canvas');
  canvas.width = canvas_width;
  canvas.height = canvas_width;
  var context = canvas.getContext('2d');
  context.fillStyle = 'rgb(0,0,0)';
  context.fillRect(0, 0, canvas_width, canvas_width);
  // Run the test.
  var test_time = drawJavaScript(canvas_width, tile_width, context);
  // Update the time element with compute and canvas manipulation times.
  time_element.innerHTML = '<b> JavaScript <br>' +
    'compute time: ' + test_time[0] + ' milliseconds<br>' +
    'canvas  time: ' + test_time[1] + ' milliseconds<br>';
}

// The Native Client version uses a shared memory object to return
// the computed rgb values for the canvas points.  As the number of
// points may vary as a user configuration, the size of the shared
// memory object may vary.
var getSharedMemorySize = function(canvas_width) {
  // The picture we are computing is a square.  Each element in the region
  // is a string like 'rgb(rrr,ggg,bbb)', or 16 bytes.
  var min_size = canvas_width * canvas_width * 16;
  // The minimum allocation size for Native Client shared memory regions is 64K.
  var bytes = 65536;
  // For convenience, we find the next power of two that holds the size.
  for (; bytes < min_size; bytes *= 2);
  return bytes;
}

// Compute the Native Client mandelbrot picture and render into canvas.
function drawNativeClient(canvas_width, tile_width, context, server) {
  // shm_size needs to be enough to keep 16 bytes per point.
  var shm_size = getSharedMemorySize(canvas_width);
  var shm = server.__shmFactory(shm_size);
  var tiles_per_row = canvas_width / tile_width;
  var call_time = 0;
  var canvas_time = 0;
  server.setup(shm);
  for (var x = 0; x < canvas_width; x += tile_width) {
    for (var y = 0; y < canvas_width; y += tile_width) {
      var before_call = new Date();
      server.tiled(x, y, tile_width, tiles_per_row);
      var str = shm.read(0, tile_width * tile_width * 16);
      var after_call = new Date();
      var difftime = after_call.getTime() - before_call.getTime();
      call_time += difftime;
      var shmpos = 0;
      before_call = new Date();
      for (var i = x; i < x + tile_width; ++i) {
        for (var j = y; j < y + tile_width; ++j) {
          context.fillStyle = str.substring(shmpos, shmpos + 16);
          shmpos += 16;
          context.fillRect(i, j, 1, 1);
        }
      }
      after_call = new Date();
      difftime = after_call.getTime() - before_call.getTime();
      canvas_time += difftime;
    }
  }
  server.shutdown();
  return [call_time, canvas_time];
}

// Compute and display the entire Native Client mandelbrot picture, including
// printing the gathered timings.
function testNativeClient(server) {
  // Set up the element where the time will be reported.
  var time_element = document.getElementById('nacl_time');
  time_element.innerHTML = '<b>Native Client';
  // Get the test parameters.
  var canvas_width = getValue('canvas_width');
  var tile_width = getValue('tile_width');
  var tiles_per_row = canvas_width / tile_width;
  // Set up the canvas object and clear it.
  var canvas = document.getElementById('nacl_canvas');
  canvas.width = canvas_width;
  canvas.height = canvas_width;
  var context = canvas.getContext('2d');
  context.fillStyle = 'rgb(0,0,0)';
  context.fillRect(0, 0, canvas_width, canvas_width);
  // Run the test.
  var test_time = drawNativeClient(canvas_width,
                                   tile_width,
                                   context,
                                   server);
  // Update the time element with compute and canvas manipulation times.
  time_element.innerHTML = '<b>Native Client<br>(' +
      tiles_per_row * tiles_per_row + ' square tiles ' +
      tile_width + ' pixels wide)<br>' +
    'compute time: ' + test_time[0] + ' milliseconds<br>' +
    'canvas  time: ' + test_time[1] + ' milliseconds<br>';
}

// Before running tests we need to ensure the Native Client module is loaded.
var startupTimeout;

function enqueueTests(tester, server) {
  tester.addTest('javascript_version', function() { testJavaScript(); });
  tester.addTest('native_client_version', function() { testNativeClient(); });
}
      //]]>
    </script>
  </head>
  <body>
    <h1> Mandelbrot: JavaScript versus Tiled Native Client </h1>
    NOTE: Set NACL_ENABLE_EXPERIMENTAL_JAVASCRIPT_APIS=1 to run this test.
    <table cellpadding=5% summary="Control panel on left, images on right">
    <tr>
      <td valign=top>
        <table border=5 cellpadding=5% summary="Values and buttons for control">
          <tr>
            <td colspan=2 align=center> <b> Control Panel </b> </td>
          </tr>
          <tr>
            <td align=center> Canvas width <br/> (in pixels) </td>
            <td>
              <input type="text" value=100 size=5 id="canvas_width" />
            </td>
          </tr>
          <tr>
            <td align=center> Tile width <br/> (in pixels) </td>
            <td>
              <input type="text" value=100 size=5 id="tile_width" />
            </td>
          </tr>
          <tr>
            <td colspan=2 align=center>
              <input type="button" onclick="testJavaScript()"
                     value="Run JavaScript" />
            </td>
          </tr>
          <tr>
            <td colspan=2 align=center>
              <input type="button" onclick="testNativeClient()"
                     value="Run Native Client" />
            </td>
          </tr>
        </table>
      </td>
      <td align=center valign=middle>
        <canvas id="js_canvas" width="100" height="100">
          <code> <em> JavaScript </em> </code>
        </canvas>
      </td>
      <td align=center valign=middle>
        <canvas id="nacl_canvas" width="100" height="100">
          <code> <em> Native Client </em> </code>
        </canvas>
      </td>
    </tr>
    <tr>
      <td> </td>
      <td align=center id="js_time"></td>
      <td align=center id="nacl_time"></td>
    </tr>
    </table>
    <b>
      First run starts automatically.  Click afterwards to re-run. <br>
      Warning: JavaScript with a canvas width of more than about 100 pixels
      may run very slowly.
    </b>
    <br/>
    <embed type="application/x-nacl" id="nacl_server"
           src="mandel_tiled.nmf" hidden="true" />
    <script type="text/javascript">
      //<![CDATA[
      var tester = new Tester();
      var server = $("nacl_server");
      enqueueTests(tester, server);
      tester.waitFor(server);
      tester.run();
      //]]>
    </script>
  </body>
</html>
